import { serve } from "https://deno.land/std@0.76.0/http/server.ts";
import {
  acceptWebSocket,
  isWebSocketCloseEvent,
  isWebSocketPingEvent,
  WebSocket,
} from "https://deno.land/std@0.76.0/ws/mod.ts";

interface Action {
  act: string;
  recvTime: number; // 第一次收到动作的时间
  seqPerFrame: number;
  id: string;
  data: string;
}

type S2cPackageData = Action[] | PlayerData | PlayerData[] | string | void;

interface S2cPackage {
  seq: number; // 严格按序递增的编号
  cmd: string; // start, act
  data: S2cPackageData;
}

interface PlayerData {
  id: string;
  x: number;
  y: number;
}

let seq = 0;

let seqPerFrame = 0;
let actions: Action[] = [];
let broadcasting = false;
let playerId = 1;
const playerMap = new Map<WebSocket, PlayerData>();

const FRAME_FPS = 25;

async function broadcast(cmd: string, data: S2cPackageData) {
  const toRemove = [];
  const rmPromises = [];
  const frameSeq = ++seq;
  const toSend = JSON.stringify({ cmd, data, seq: frameSeq });

  for (const [p] of playerMap) {
    try {
      p.send(toSend);
    } catch (err) {
      console.error("failed to send", err);
      if (!p.isClosed) {
        rmPromises.push(p.close(1000).catch(console.error));
      }
      toRemove.push(p);
    }
  }
  await Promise.all(rmPromises);
  for (const rm of toRemove) {
    playerMap.delete(rm);
  }
}

async function sendMessage(sock: WebSocket, msg: S2cPackage) {
  try {
    sock.send(JSON.stringify(msg));
  } catch (err) {
    console.error("failed to send", err);
    if (!sock.isClosed) {
      await sock.close(1000).catch(console.error);
    }
    playerMap.delete(sock);
  }
}

function handleMsg(sock: WebSocket, msg: string) {
  const msgObj = JSON.parse(msg);
  const { cmd, data, id } = msgObj;
  console.log("handleMsg", id, cmd, data);
  if (cmd === "act") {
    const act: Action = {
      recvTime: Date.now(),
      seqPerFrame: ++seqPerFrame,
      act: data.act,
      data: data.data,
      id,
    };
    actions.push(act);
  }
}

async function broadcastFrame() {
  broadcasting = true;
  await broadcast("act", actions);
  actions = [];
  broadcasting = false;
}

function startBroadcast() {
  setInterval(() => {
    if (!broadcasting) {
      broadcastFrame();
    }
  }, 1000 / FRAME_FPS);
}

async function handleWs(sock: WebSocket) {
  console.log("socket connected!");
  const newId = "" + playerId++;
  const x = Math.random() * 800;
  const y = Math.random() * 600;
  playerMap.set(sock, { id: newId, x, y });
  sendMessage(sock, { seq: ++seq, cmd: "id", data: { id: newId, x, y } });
  const allPlayersId: PlayerData[] = [];
  for (const v of playerMap.values()) {
    allPlayersId.push(v);
  }
  sendMessage(sock, { seq: ++seq, cmd: "allplayers", data: allPlayersId });
  broadcast("add", { id: newId, x, y });
  try {
    for await (const ev of sock) {
      if (typeof ev === "string") {
        // text message.
        console.log("ws:Text", ev);
        handleMsg(sock, ev);
      } else if (ev instanceof Uint8Array) {
        // binary message.
        console.log("ws:Binary", ev);
      } else if (isWebSocketPingEvent(ev)) {
        const [, body] = ev;
        // ping.
        console.log("ws:Ping", body);
      } else if (isWebSocketCloseEvent(ev)) {
        // close.
        const { code, reason } = ev;
        console.log("ws:Close", code, reason);
        playerMap.delete(sock);
      }
    }
  } catch (err) {
    console.error(`failed to receive frame: ${err}`);

    if (!sock.isClosed) {
      await sock.close(1000).catch(console.error);
    }
  }
}

if (import.meta.main) {
  /** websocket echo server */
  const port = Deno.args[0] || "8080";
  console.log(`websocket server is running on :${port}`);
  startBroadcast();
  for await (const req of serve(`:${port}`)) {
    const { conn, r: bufReader, w: bufWriter, headers } = req;
    acceptWebSocket({
      conn,
      bufReader,
      bufWriter,
      headers,
    })
      .then(handleWs)
      .catch(async (err) => {
        console.error(`failed to accept websocket: ${err}`);
        await req.respond({ status: 400 });
      });
  }
}
